home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 6 code / TCP / NewsWatcher / NW Source / Source / newsrc.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-05-23  |  35.6 KB  |  1,389 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     newsrc.c
  4.  
  5.     This module handles newsrc-format files, including opening
  6.     and saving user group lists from and to disk in newsrc format,
  7.     and getting and sending newsrc files from and to remote hosts. 
  8.     
  9.     Copyright © 1994-1995, Northwestern University.
  10.  
  11. ----------------------------------------------------------------------------*/
  12.  
  13. #include <stdio.h>
  14. #include <string.h>
  15. #include <stdlib.h>
  16. #include <errno.h>
  17. #include <ctype.h>
  18.  
  19. #include "glob.h"
  20. #include "newsrc.h"
  21. #include "dialog.h"
  22. #include "prefs.h"
  23. #include "ftp.h"
  24. #include "group.h"
  25. #include "full.h"
  26. #include "newswatcher.h"
  27. #include "mark.h"
  28. #include "news.h"
  29. #include "sfutil.h"
  30. #include "status.h"
  31. #include "wind.h"
  32. #include "text.h"
  33. #include "memutil.h"
  34. #include "fileutil.h"
  35. #include "strutil.h"
  36. #include "resutil.h"
  37. #include "ic.h"
  38.  
  39.  
  40.  
  41. #define kHostDlg            129            /* Remote host dialog */
  42. #define kRemoteHost            4
  43. #define kRemoteUsername        6
  44. #define kRemotePassword        8
  45. #define kRemotePath            10
  46. #define kAutoGetPut            11
  47. #define kSavePassword        12
  48.  
  49. #define kCheckSaveID        133            /* Save confirm dialog */
  50.  
  51. #define kDeletedGroupsDlg        131            /* Deleted groups dialog */
  52. #define KDelScrollingTextItem    4            /* item number of scrolling text field */
  53.  
  54.  
  55.  
  56. /* The following globals are used when parsing newsrc lists. */
  57.  
  58. static TGroup     **gUserGroupArray;            /* handle to user group array under construction */
  59. static short     gNumUserGroups;                /* number of user groups */
  60. static short     gNumUserGroupsAllocated;    /* number of user groups allocated */
  61. static Handle    gDeleted;                    /* handle to list of deleted groups */
  62. static long        gDeletedLen;                /* length of list of deleted groups */
  63. static long        gDeletedAllocated;            /* number of bytes allocated for gDeleted */
  64. static char        *gUnsubscribed;                /* pointer to next location to move unsubscribed groups */
  65. static char     *gPos;                        /* current parsing position in newsrc list */
  66. static TGroup     gTheGroup;                    /* current group under construction */
  67.  
  68. /* The following global variables are used when constructing newsrc lists. */
  69.  
  70. static Handle    gNewsrc;                    /* newsrc list under construction */
  71. static long        gNewsrcLength;                /* length of newsrc */
  72. static long        gNewsrcAllocated;            /* number of bytes allocated in newsrc */
  73.  
  74.  
  75.  
  76. /*----------------------------------------------------------------------------
  77.     DoHostDialog 
  78.     
  79.     Present the remote host dialog.
  80.     
  81.     Exit:    function result = error code.
  82. ----------------------------------------------------------------------------*/
  83.  
  84. static OSErr DoHostDialog (void)
  85. {
  86.     DialogPtr dlg = nil;
  87.     short item;
  88.     CStr255 tempStr;
  89.     short len;
  90.     CStr255 host;
  91.     CStr255 username;
  92.     char path[32];
  93.     char password[32];
  94.     OSErr err = noErr;
  95.     
  96.     err = MyGetNewDialog(kHostDlg, ok, cancel, &dlg);
  97.     if (err != noErr) return err;
  98.     RestoreMovableModalDialogPosition(dlg, gPrefs.hostLoc);
  99.     strcpy(host, gPrefs.ftpNewsrcHost);
  100.     DlgSetCString(dlg, kRemoteHost, host);
  101.     SetItemHostAddress(dlg, kRemoteHost);
  102.     SetItemMaxLength(dlg, kRemoteHost, 255);
  103.     strcpy(username, gPrefs.ftpNewsrcUsername);
  104.     DlgSetCString(dlg, kRemoteUsername, username);
  105.     SetItemUSAsciiNoBlank(dlg, kRemoteUsername);
  106.     SetItemMaxLength(dlg, kRemoteUsername, 255);
  107.     strcpy(password, gPrefs.ftpNewsrcPassword);
  108.     len = strlen(password);
  109.     memset(tempStr, '•', len);
  110.     tempStr[len] = 0;
  111.     DlgSetCString(dlg, kRemotePassword, tempStr);
  112.     SetItemPassword(dlg, kRemotePassword, password);
  113.     SetItemMaxLength(dlg, kRemotePassword, 31);
  114.     strcpy(path, gPrefs.ftpNewsrcPath);
  115.     DlgSetCString(dlg, kRemotePath, path);
  116.     SetItemMaxLength(dlg, kRemotePath, 31);
  117.     DlgSetCheck(dlg, kAutoGetPut, gPrefs.autoFetchNewsrc);
  118.     DlgSetCheck(dlg, kSavePassword, gPrefs.saveFtpNewsrcPassword);
  119.     if (*host == 0) {
  120.         SelectDialogItemText(dlg, kRemoteHost, 0, 0);
  121.     } else if (*username == 0) {
  122.         SelectDialogItemText(dlg, kRemoteUsername, 0, 0);
  123.     } else if (*password == 0) {
  124.         SelectDialogItemText(dlg, kRemotePassword, 0, 0);
  125.     } else if (*path == 0) {
  126.         SelectDialogItemText(dlg, kRemotePath, 0, 0);
  127.     } else {
  128.         SelectDialogItemText(dlg, kRemoteHost, 0, 0x7fff);
  129.     }
  130.     
  131.     do {
  132.         DlgEnableItem(dlg, ok, *host != 0 && *username != 0 && *password != 0 && *path != 0);
  133.         MyMovableModalDialog(dlg, DialogFilter, &item);
  134.         switch (item) {
  135.             case kRemoteHost:
  136.                 DlgGetCString(dlg, item, host);
  137.                 break;
  138.             case kRemoteUsername:
  139.                 DlgGetCString(dlg, item, username);
  140.                 break;
  141.             case kRemotePath:
  142.                 DlgGetCString(dlg, item, path);
  143.                 break;
  144.             case kAutoGetPut:
  145.             case kSavePassword:
  146.                 DlgToggleCheck(dlg, item);
  147.                 break;
  148.         }
  149.     } while (item != ok && item != cancel);
  150.  
  151.     if (item == ok) {
  152.         strcpy(gPrefs.ftpNewsrcHost, host);
  153.         strcpy(gPrefs.ftpNewsrcUsername, username);
  154.         strcpy(gPrefs.ftpNewsrcPassword, password);
  155.         strcpy(gPrefs.ftpNewsrcPath, path);
  156.         gPrefs.autoFetchNewsrc = DlgGetCheck(dlg, kAutoGetPut);
  157.         gPrefs.saveFtpNewsrcPassword = DlgGetCheck(dlg, kSavePassword);
  158.     }
  159.     SaveMovableModalDialogPosition(dlg, &gPrefs.hostLoc);
  160.     err = DoClose(dlg);
  161.     if (err != noErr) return err;
  162.     return item == ok ? noErr : userCanceledErr;
  163. }
  164.  
  165.  
  166.  
  167. /*----------------------------------------------------------------------------
  168.     Skip 
  169.     
  170.     Skip white space in a newsrc line.
  171. ----------------------------------------------------------------------------*/
  172.  
  173. static void Skip (void)
  174. {
  175.     while (*gPos == ' ' || *gPos == '\t') gPos++;
  176. }
  177.  
  178.  
  179.  
  180. /*----------------------------------------------------------------------------
  181.     GetUnreadList 
  182.     
  183.     Parse the list of read article ranges in a newsrc line and
  184.     convert it to a linked list of unread article ranges.
  185.  
  186.     Exit:    function result = error code.
  187.             syntaxError = true if syntax error.
  188.             gTheGroup.unread = handle to unread list.
  189.             gTheGroup.lastMess = highest article number read, or 0 if 
  190.                 unread list is empty.
  191. ----------------------------------------------------------------------------*/
  192.  
  193. static OSErr GetUnreadList (Boolean *syntaxError)
  194. {
  195.     long firstUnread, firstRead, lastRead;
  196.     OSErr err = noErr;
  197.  
  198.     gTheGroup.unread = nil;
  199.     gTheGroup.numUnread = 0;
  200.     
  201.     firstUnread = 1;
  202.     lastRead = 0;
  203.     Skip();
  204.     
  205.     while (*gPos != CR) {
  206.         Skip();
  207.         if (!isdigit(*gPos)) goto exit1;
  208.         firstRead = CrackNum(&gPos);
  209.         if (firstRead == 0) firstRead = 1;
  210.         if (firstRead < firstUnread) goto exit1;
  211.         Skip();
  212.         if (*gPos == '-') {
  213.             gPos++;
  214.             Skip();
  215.             if (!isdigit(*gPos)) goto exit1;
  216.             lastRead = CrackNum(&gPos);
  217.             if (lastRead < firstRead) goto exit1;
  218.         } else {
  219.             lastRead = firstRead;
  220.         }
  221.         if (firstUnread < firstRead) {
  222.             err = AppendUnreadRange(firstUnread, firstRead-1, &gTheGroup);
  223.             if (err != noErr) goto exit2;
  224.         }
  225.         firstUnread = lastRead+1;
  226.         Skip();
  227.         if (*gPos == ',') {
  228.             gPos++;
  229.             Skip();
  230.         }
  231.     }
  232.     
  233.     gPos++;
  234.     err = AppendUnreadRange(firstUnread, 0x7fffffff, &gTheGroup);
  235.     if (err != noErr) goto exit2;
  236.     gTheGroup.lastMess = lastRead;
  237.     *syntaxError = false;
  238.     return noErr;
  239.  
  240. exit1:
  241.  
  242.     DisposeGroupUnreadList(&gTheGroup);
  243.     *syntaxError = true;
  244.     return noErr;
  245.  
  246. exit2:
  247.  
  248.     DisposeGroupUnreadList(&gTheGroup);
  249.     return err;
  250. }
  251.  
  252.  
  253. /*----------------------------------------------------------------------------
  254.     RecordDeletedGroup 
  255.     
  256.     Record a user error message for a group which was deleted while
  257.     parsing a newsrc file.
  258.     
  259.     Entry:    groupName = the group name.
  260.             reason = the reason the group was deleted:
  261.                 1: Syntax error in newsrc line.
  262.                 2: Group not in full group list.
  263.                 3: Group deleted on server.
  264.     
  265.     Exit:    function result = error code.
  266. ----------------------------------------------------------------------------*/
  267.  
  268. static OSErr RecordDeletedGroup (char *groupName, short reason)
  269. {
  270.     CStr255 reasonStr;
  271.     char msg[512];
  272.     short len, reasonStrIndex;
  273.     OSErr err = noErr;
  274.  
  275.     switch (reason) {
  276.         case 1:
  277.             reasonStrIndex = kStrSyntaxErr;
  278.             break;
  279.         case 2:
  280.             reasonStrIndex = kStrNotInFullGroupList;
  281.             break;
  282.         case 3:
  283.             reasonStrIndex = kStrGroupDeletedOnServer;
  284.             break;
  285.     }
  286.     GetCString(reasonStrIndex, reasonStr);
  287.     sprintf(msg, "%s: %s\r", groupName, reasonStr);
  288.     len = strlen(msg);
  289.     if (gDeletedLen + len > gDeletedAllocated) {
  290.         gDeletedAllocated += 1000;
  291.         err = MySetHandleSize(gDeleted, gDeletedAllocated);
  292.         if (err != noErr) return err;
  293.     }
  294.     BlockMoveData(msg, *gDeleted + gDeletedLen, len);
  295.     gDeletedLen += len;
  296.     return noErr;
  297. }
  298.  
  299.  
  300.  
  301. /*----------------------------------------------------------------------------
  302.     ProcessOneNewsrcLine 
  303.     
  304.     Parse and processe one line from a newsrc list.
  305.  
  306.     Entry:    gPos = pointer to beginning of newsrc line.
  307.  
  308.     Exit:    function result = error code.
  309.             gPos = pointer to beginning of next newsrc line.
  310.  
  311.     There are four possible kinds of newsrc lines:
  312.  
  313.     1. Lines with syntax errors. These lines are recorded in the gDeleted
  314.        array to be reported to the user later.
  315.  
  316.     2. Lines for deleted groups (group name not in full group list). 
  317.        These lines are recorded in the gDeleted array to be reported to
  318.        the user later.
  319.  
  320.     3. Subscribed lines. These lines are parsed and appended to the end of the
  321.        gUserGroupArray, with the following TGroup fields initialized:
  322.  
  323.        nameOffset = offset in gGroupNames of group name.
  324.        firstMess = 1.
  325.        lastMess = highest article number read on newsrc line, or 0 if read article
  326.           list is empty.
  327.        unread = unread list built assuming the range of articles in the group
  328.          is [1,maxlong]. This will be adjusted later when we learn the real
  329.          range of articles in the group.
  330.        status = 'x'. This indicates that we need to learn the range of articles
  331.          for the group.
  332.        onlyRedrawCount = false.
  333.  
  334.        The firstMess, lastMess, and numUnread fields are reset later when
  335.        we learn the real range of articles in the group.
  336.  
  337.     4. Unsubscribed lines. These lines are left in the newsrc list, moved down
  338.        so that they are contiguous. NewsWatcher doesn't do anything with these
  339.        lines except append them to the end of the newsrc file when it is later
  340.        sent to a remote host. This is for compatibility with UNIX newsreaders.
  341. ----------------------------------------------------------------------------*/
  342.  
  343. static OSErr ProcessOneNewsrcLine(void)
  344. {
  345.     CStr255 groupName;
  346.     char *groupNameStart, *lineStart;
  347.     short index;
  348.     long len;
  349.     OSErr err = noErr;
  350.     Boolean syntaxError;
  351.  
  352.     lineStart = gPos;
  353.     Skip();
  354.     groupNameStart = gPos;
  355.     while (*gPos != ':' && *gPos != '!' && *gPos != CR) gPos++;
  356.     len = gPos - groupNameStart;
  357.     if (len > 255) {
  358.         BlockMoveData(groupNameStart, groupName, 255);
  359.         groupName[255] = 0;
  360.         err = RecordDeletedGroup(groupName, 1);
  361.         if (err != noErr) return err;
  362.         goto exit;
  363.     }
  364.     BlockMoveData(groupNameStart, groupName, len);
  365.     groupName[len] = 0;
  366.     if (len == 0) {
  367.         GetCString(kStrMissingGroupName, groupName);
  368.         err = RecordDeletedGroup(groupName, 1);
  369.         if (err != noErr) return err;
  370.         goto exit;
  371.     }
  372.     
  373.     if (*gPos == ':') {
  374.     
  375.         /* subscribed line - parse it and append the new group to the end
  376.            of gUserGroupArray. */
  377.  
  378.         gPos++;
  379.         index = FindGroupIndex(groupName);
  380.         if (index == -1) {
  381.             err = RecordDeletedGroup(groupName, 2);
  382.             if (err != noErr) return err;
  383.             goto exit;
  384.         }
  385.         gTheGroup.nameOffset = (*gFullGroupArray)[index].nameOffset;
  386.         gTheGroup.firstMess = 1;
  387.         err = GetUnreadList(&syntaxError);
  388.         if (err != noErr) return err;
  389.         if (syntaxError) {
  390.             err = RecordDeletedGroup(groupName, 1);
  391.             if (err != noErr) return err;
  392.             goto exit;
  393.         }
  394.         gTheGroup.status = 'x';
  395.         gTheGroup.onlyRedrawCount = false;
  396.         if (gNumUserGroups >= gNumUserGroupsAllocated) {
  397.             gNumUserGroupsAllocated += 50;
  398.             err = MySetHandleSize(gUserGroupArray, sizeof(TGroup)*gNumUserGroupsAllocated);
  399.             if (err != noErr) return err;
  400.         }
  401.         (*gUserGroupArray)[gNumUserGroups] = gTheGroup;
  402.         gNumUserGroups++;
  403.         return noErr;
  404.                 
  405.     } else if (*gPos == '!') {
  406.     
  407.         /* unsubscribed line - copy the line as is to the end of the
  408.            gUnsubscribed memory block. */
  409.            
  410.         while (*gPos != CR) gPos++;
  411.         *gPos++;
  412.         len = gPos - lineStart;
  413.         BlockMoveData(lineStart, gUnsubscribed, len);
  414.         gUnsubscribed += len;
  415.         return noErr;
  416.         
  417.     } else {
  418.     
  419.         err = RecordDeletedGroup(groupName, 1);
  420.         if (err != noErr) return err;
  421.         return noErr;
  422.     
  423.     }
  424.  
  425. exit:
  426.  
  427.     /* syntax error - skip this line. */
  428.  
  429.     while (*gPos != CR) gPos++;
  430.     gPos++;
  431.     return noErr;
  432. }
  433.  
  434.  
  435.  
  436. /*----------------------------------------------------------------------------
  437.     GetArticleRangeInfo 
  438.     
  439.     Query the NNTP server to get current article range info for each group 
  440.     in a new user group array.
  441.  
  442.     Exit:    function result = error code.
  443. ----------------------------------------------------------------------------*/
  444.  
  445. static OSErr GetArticleRangeInfo (void)
  446. {
  447.     short i = 0;
  448.     short numDeleted = 0;
  449.     char *groupName;
  450.     OSErr err = noErr;
  451.     char state;
  452.     
  453.     err = DisplayStatusMessageNumber(kStrCheckingForNewArticlesStatusMsg);
  454.     if (err != noErr) return err;
  455.  
  456.     err = GetGroupArrayArticleRanges(gUserGroupArray, gNumUserGroups);
  457.     if (err != noErr) return err;
  458.  
  459.     while (i < gNumUserGroups) {
  460.         gTheGroup = (*gUserGroupArray)[i];
  461.         if (gTheGroup.status == 'x') {
  462.             AdjustUnreadList(&gTheGroup);
  463.             gTheGroup.status = ' ';
  464.             (*gUserGroupArray)[i] = gTheGroup;
  465.             i++;
  466.         } else if (gTheGroup.status == 'd') {
  467.             DisposeGroupUnreadList(&gTheGroup);
  468.             gNumUserGroups--;
  469.             if (i < gNumUserGroups) {
  470.                 BlockMoveData(*gUserGroupArray+i+1, *gUserGroupArray+i,
  471.                     sizeof(TGroup)*(gNumUserGroups-i));
  472.             }
  473.             numDeleted++;
  474.             state = MyHGetState(gGroupNames);
  475.             MyHLock(gGroupNames);
  476.             groupName = *gGroupNames + gTheGroup.nameOffset;
  477.             err = RecordDeletedGroup(groupName, 3);
  478.             MyHSetState(gGroupNames, state);
  479.             if (err != noErr) return err;
  480.         }
  481.     }
  482.     
  483.     if (numDeleted > 0)
  484.         MySetHandleSize(gUserGroupArray, sizeof(TGroup)*gNumUserGroups);
  485.     
  486.     return noErr;
  487. }
  488.  
  489.  
  490.  
  491. /*----------------------------------------------------------------------------
  492.     MakeUserGroupArrayFromNewsrc 
  493.     
  494.     Create a new user group array from a newsrc-format list.
  495.  
  496.     Entry:    newsrc = handle to newsrc-format list.
  497.  
  498.     Exit:    function result = error code.
  499.             newsrc = handle to list of unsubscribed groups.
  500.             *groupArray = handle to new user group array.
  501.             *numGroups = number of groups in array.
  502.             *deleted = handle to list of deleted groups.
  503. ----------------------------------------------------------------------------*/
  504.  
  505. static OSErr MakeUserGroupArrayFromNewsrc (Handle newsrc, TGroup ***groupArray, 
  506.     short *numGroups, Handle *deleted)
  507. {    
  508.     char *gPosEnd;
  509.     OSErr err = noErr;
  510.     char state;
  511.  
  512.     state = MyHGetState(newsrc);
  513.     MyHLockHi(newsrc);
  514.  
  515.     err = MyNewHandle(50*sizeof(TGroup), &gUserGroupArray);
  516.     if (err != noErr) goto exit;
  517.     gNumUserGroups = 0;
  518.     gNumUserGroupsAllocated = 50;
  519.     
  520.     gUnsubscribed = *newsrc;
  521.     
  522.     err = MyNewHandle(0, &gDeleted);
  523.     if (err != noErr) goto exit;
  524.     gDeletedLen = 0;
  525.     gDeletedAllocated = 0;
  526.  
  527.     gPos = *newsrc;
  528.     gPosEnd = gPos + MyGetHandleSize(newsrc);
  529.     while (gPos < gPosEnd) {
  530.         err = ProcessOneNewsrcLine();
  531.         if (err != noErr) goto exit;
  532.     }
  533.     
  534.     MyHSetState(newsrc, state);
  535.     MySetHandleSize(newsrc, gUnsubscribed - *newsrc);
  536.     
  537.     MySetHandleSize(gUserGroupArray, gNumUserGroups*sizeof(TGroup));
  538.  
  539.     err = GetArticleRangeInfo();
  540.     if (err != noErr) goto exit;
  541.     
  542.     *groupArray = gUserGroupArray;
  543.     *numGroups = gNumUserGroups;
  544.     MySetHandleSize(gDeleted, gDeletedLen);
  545.     *deleted = gDeleted;
  546.     return noErr;
  547.     
  548. exit:
  549.     
  550.     MyHSetState(newsrc, state);
  551.     DisposeGroupArray(gUserGroupArray, gNumUserGroups);
  552.     MyDisposeHandle(gDeleted);
  553.     *groupArray = nil;
  554.     *numGroups = 0;
  555.     *deleted = nil;
  556.     return err;
  557. }
  558.  
  559.  
  560. /*----------------------------------------------------------------------------
  561.     MakeUserGroupWindowFromNewsrc 
  562.     
  563.     Create a new user group list window from a newsrc-format list.
  564.  
  565.     Entry:    newsrc = handle to newsrc-format list.
  566.             title = window title.
  567.             pos = pointer to saved window postion.
  568.             
  569.     Exit:    function result = error code.
  570.             *theWindow = pointer to new user group list window
  571.             
  572.     Note:    The newsrc block passed as a parameter to this function is either
  573.             disposed or reused by the function. The caller must *NOT* dispose
  574.             or reuse this block.
  575. ----------------------------------------------------------------------------*/
  576.  
  577. static OSErr MakeUserGroupWindowFromNewsrc (Handle newsrc, StringPtr title,
  578.     TSavedWindPos *pos, WindowPtr *theWindow)
  579. {
  580.     TGroup **groupArray;
  581.     Handle deleted = nil;
  582.     WindowPtr wind;
  583.     TWindow **info;
  584.     short numGroups;
  585.     DialogPtr dlg = nil;
  586.     short item;
  587.     OSErr err = noErr;
  588.  
  589.     err = MakeUserGroupArrayFromNewsrc(newsrc, &groupArray, &numGroups, &deleted);
  590.     if (err != noErr) goto exit;
  591.     
  592.     err = MakeUserGroupWindow(title, groupArray, numGroups, pos, &wind);
  593.     if (err != noErr) goto exit;
  594.     
  595.     info = (TWindow**)GetWRefCon(wind);
  596.     
  597.     if (MyGetHandleSize(newsrc) == 0) {
  598.         MyDisposeHandle(newsrc);
  599.     } else {        
  600.         (**info).unsubscribed = newsrc;
  601.     }
  602.     newsrc = nil;
  603.  
  604.     if (MyGetHandleSize(deleted) != 0) {
  605.         MyICReadSharedPrefs(kICScreenFont);
  606.         (**info).changed = true;
  607.         SysBeep(0);
  608.         err = MyGetNewDialog(kDeletedGroupsDlg, ok, 0, &dlg);
  609.         if (err != noErr) goto exit;
  610.         RestoreMovableModalDialogPosition(dlg, gPrefs.delGroupsLoc);
  611.         SetItemScrollingTextField(dlg, KDelScrollingTextItem,
  612.             gPrefs.textFont, gPrefs.textSize, true);
  613.         MyHLock(deleted);
  614.         DlgSetScrollingText(dlg, KDelScrollingTextItem, *deleted,
  615.             MyGetHandleSize(deleted));
  616.         DlgSetScrollingTextSelection(dlg, KDelScrollingTextItem, 0, 0);
  617.         MyMovableModalDialog(dlg, DialogFilter, &item);
  618.         SaveMovableModalDialogPosition(dlg, &gPrefs.delGroupsLoc);
  619.         err = DoClose(dlg);
  620.         if (err != noErr) goto exit;
  621.     }
  622.     MyDisposeHandle(deleted);
  623.     
  624.     *theWindow = wind;
  625.     return noErr;
  626.     
  627. exit:
  628.  
  629.     MyDisposeHandle(deleted);
  630.     MyDisposeHandle(newsrc);
  631.     return err;
  632. }
  633.  
  634.  
  635.  
  636. /*----------------------------------------------------------------------------
  637.     OpenUserGroupListFile 
  638.     
  639.     Open a user group list from a disk file.
  640.  
  641.     Entry:    fSpec = the file to be opened.
  642.     
  643.     Exit:    function result = error code.
  644. ----------------------------------------------------------------------------*/
  645.  
  646. OSErr OpenUserGroupListFile (FSSpec *fSpec)
  647. {
  648.     OSErr err = noErr;
  649.     short fRefNum = 0;
  650.     short rRefNum = 0;
  651.     long length;
  652.     Handle newsrc = nil;
  653.     WindowPtr wind;
  654.     TWindowKind kind;
  655.     TWindow **info;
  656.     FSSpec windFile;
  657.     TSavedWindPos pos;
  658.     Handle h;
  659.     Boolean wasChanged;
  660.     AliasHandle alias;
  661.  
  662.     /* Check to see if the file is already open. If it is, bring its
  663.        window to the front. */
  664.     
  665.     for (wind = FrontWindow(); 
  666.         wind != nil; 
  667.         wind = (WindowPtr)((WindowPeek)wind)->nextWindow) 
  668.     {
  669.         kind = GetMyWindowKind(wind);
  670.         if (kind == kGroup) {
  671.             info = (TWindow**)GetWRefCon(wind);
  672.             if ((**info).groupKind == kUserGroup && (**info).alias != nil) {
  673.                 err = ResolveAlias(nil, (**info).alias, &windFile, &wasChanged);
  674.                 if (err == noErr && IsEqualFSSpec(&windFile, fSpec)) {
  675.                     MySelectWindow(wind);
  676.                     return noErr; 
  677.                 }
  678.             }
  679.         }
  680.     }
  681.                
  682.     /* Open the file in a new window. */    
  683.     
  684.     err = FSpOpenDF(fSpec, fsRdPerm, &fRefNum);
  685.     if (err != noErr) goto exit;
  686.     err = GetEOF(fRefNum, &length);
  687.     if (err != noErr) goto exit;
  688.     err = MyNewHandle(length, &newsrc);
  689.     if (err != noErr) goto exit;
  690.     MyHLock(newsrc);
  691.     err = FSRead(fRefNum, &length, *newsrc);
  692.     MyHUnlock(newsrc);
  693.     if (err != noErr) goto exit;
  694.     MyFSClose(fRefNum, nil);
  695.     fRefNum = 0;
  696.     
  697.     pos.valid = false;
  698.     err = MyFSpOpenResFile(fSpec, fsRdPerm, &rRefNum);
  699.     if (err == noErr) {
  700.         err = MyGet1Resource('WPOS', 128, &h);
  701.         if (err == noErr) {
  702.             if (MyGetHandleSize(h) == sizeof(Point)) {
  703.                 pos.valid = true;
  704.                 pos.oldFormat = true;
  705.                 BlockMoveData(*h, &pos.userState, sizeof(Point));
  706.             } else {
  707.                 BlockMoveData(*h, &pos, sizeof(TSavedWindPos));
  708.             }
  709.         }
  710.         MyCloseResFile(rRefNum);
  711.     }
  712.     
  713.     err = MakeUserGroupWindowFromNewsrc(newsrc, fSpec->name, &pos, &wind);
  714.     newsrc = nil;
  715.     if (err != noErr) goto exit;
  716.     
  717.     info = (TWindow**)GetWRefCon(wind);
  718.     err = NewAlias(nil, fSpec, &alias);
  719.     if (err != noErr) goto exit;
  720.     (**info).alias = alias;
  721.     
  722.     return noErr;
  723.     
  724. exit:
  725.  
  726.     MyDisposeHandle(newsrc);
  727.     if (fRefNum != 0) MyFSClose(fRefNum, nil);
  728.     return err;
  729. }
  730.  
  731.  
  732.  
  733. /*----------------------------------------------------------------------------
  734.     OpenFTPStream 
  735.     
  736.     Open an FTP stream.
  737.  
  738.     Entry:    autoFetch = true if startup call to autoFetch group list from host
  739.                 or group list window close call to send autoFetched group
  740.                 list back to host.
  741.             statusMsgIndex = index in STR# 128 of status message.
  742.             
  743.     Exit:    function result = error code.
  744.             *stream = reference to opened stream.
  745. ----------------------------------------------------------------------------*/
  746.  
  747. static OSErr OpenFTPStream (Boolean autoFetch, short statusMsgIndex,
  748.     FtpStreamRef *stream)
  749. {
  750.     OSErr err = noErr;
  751.     NetServerErrInfo serverErrInfo;
  752.  
  753.     while (true) {
  754.         if (!autoFetch || *gPrefs.ftpNewsrcHost == 0 || 
  755.             *gPrefs.ftpNewsrcUsername == 0 ||
  756.             *gPrefs.ftpNewsrcPassword == 0 || *gPrefs.ftpNewsrcPath == 0)
  757.         {
  758.             err = DoHostDialog();
  759.             if (err != noErr) return err;
  760.         }
  761.         err = DisplayStatusMessageNumber(statusMsgIndex);
  762.         if (err != noErr) return err;
  763.         err = FtpOpen(gPrefs.ftpNewsrcHost, gPrefs.ftpNewsrcUsername, 
  764.             gPrefs.ftpNewsrcPassword, false, stream);
  765.         if (err == ftpServerErr) {
  766.             FtpGetServerErrInfo(*stream, &serverErrInfo);
  767.             FtpClose(*stream);
  768.             *stream = nil;
  769.             if (serverErrInfo.responseCode != 530) break;
  770.             *gPrefs.ftpNewsrcPassword = 0;
  771.             err = ServerErrorMessage(kStrFTP, serverErrInfo.command, serverErrInfo.response);
  772.             if (err != noErr) return err;
  773.             if (serverErrInfo.responseCode == 530) continue;
  774.             return userCanceledErr;
  775.         } else if (err != noErr) {
  776.             SaveNetErrorInfo(kStrFTP, gPrefs.ftpNewsrcHost);
  777.             return err;
  778.         } else {
  779.             return noErr;
  780.         }
  781.     }
  782.     
  783.     return noErr;
  784. }
  785.  
  786.  
  787.  
  788. /*----------------------------------------------------------------------------
  789.     GetNewsrcFileFromHost 
  790.     
  791.     FTP newsrc file from remote host.
  792.  
  793.     Entry:    autoFetch = true if startup call to autoFetch group list from host.
  794.             
  795.     Exit:    function result = error code.
  796.             *newsrc = handle to fetched newsrc file.
  797. ----------------------------------------------------------------------------*/
  798.  
  799. static OSErr GetNewsrcFileFromHost (Boolean autoFetch, Handle *newsrc)
  800. {
  801.     FtpStreamRef stream = nil;
  802.     OSErr err = noErr;
  803.     NetServerErrInfo serverErrInfo;
  804.  
  805.     *newsrc = nil;
  806.     
  807.     err = OpenFTPStream(autoFetch, kStrGettingGroupListFromHostStatusMsg, &stream);
  808.     if (err != noErr) return err;
  809.     
  810.     err = FtpGetFile(stream, gPrefs.ftpNewsrcPath, true, newsrc);
  811.     if (err != noErr) goto exit;
  812.     
  813.     err = FtpClose(stream);
  814.     if (err != noErr) goto exit;
  815.  
  816.     return noErr;
  817.     
  818. exit:
  819.  
  820.     MyDisposeHandle(*newsrc);
  821.     if (err == ftpServerErr) {
  822.         FtpGetServerErrInfo(stream, &serverErrInfo);
  823.         FtpClose(stream);
  824.         err = ServerErrorMessage(kStrFTP, serverErrInfo.command, serverErrInfo.response);
  825.         if (err != noErr) return err;
  826.         return userCanceledErr;
  827.     } else {
  828.         SaveNetErrorInfo(kStrFTP, gPrefs.ftpNewsrcHost);
  829.         return err;
  830.     }
  831.     
  832. }
  833.  
  834.  
  835.  
  836. /*----------------------------------------------------------------------------
  837.     GetGroupListFromHost 
  838.     
  839.     Get a group list from a host.
  840.  
  841.     Entry:    autoFetch = true if startup call to autoFetch group list from host.
  842.     
  843.     Exit:    function result = error code.
  844. ----------------------------------------------------------------------------*/
  845.  
  846. static OSErr GetGroupListFromHost (Boolean autoFetch)
  847. {
  848.     Handle newsrc = nil;
  849.     Str255 title;
  850.     OSErr err = noErr;
  851.     TSavedWindPos pos;
  852.     WindowPtr wind;
  853.     TWindow **info;
  854.  
  855.     err = GetNewsrcFileFromHost(autoFetch, &newsrc);
  856.     if (err != noErr) return err;
  857.     
  858.     if (autoFetch) {
  859.         pos = gPrefs.autoFetchWindPos;
  860.     } else {
  861.         pos.valid = false;
  862.     }
  863.         
  864.     GetPString(kStrGetFromHostWindowTitle, title);
  865.     err = MakeUserGroupWindowFromNewsrc(newsrc, title, &pos, &wind);
  866.     newsrc = nil;
  867.     if (err != noErr) goto exit;
  868.     
  869.     info = (TWindow**)GetWRefCon(wind);
  870.     (**info).changed = false;
  871.  
  872.     if (autoFetch) {
  873.         strcpy(gAutoFetchHost, gPrefs.ftpNewsrcHost);
  874.         strcpy(gAutoFetchUsername, gPrefs.ftpNewsrcUsername);
  875.         strcpy(gAutoFetchPassword, gPrefs.ftpNewsrcPassword);
  876.         strcpy(gAutoFetchPath, gPrefs.ftpNewsrcPath);
  877.         (**info).autoFetched = true;
  878.     }
  879.     
  880.     return noErr;
  881.  
  882. exit:
  883.  
  884.     MyDisposeHandle(newsrc);
  885.     return err;
  886. }
  887.  
  888.  
  889.  
  890. /*----------------------------------------------------------------------------
  891.     DoGetGroupListFromHost 
  892.     
  893.     Handle the "Get Group List from Host" command.
  894.     
  895.     Exit:    function result = error code.
  896. ----------------------------------------------------------------------------*/
  897.  
  898. OSErr DoGetGroupListFromHost (void)
  899. {
  900.     return GetGroupListFromHost(false);
  901. }
  902.  
  903.  
  904.  
  905. /*----------------------------------------------------------------------------
  906.     AutoFetchNewsrcFromHost 
  907.     
  908.     Auto-fetch a newsrc from a host at startup.
  909.     
  910.     Exit:    function result = error code.
  911. ----------------------------------------------------------------------------*/
  912.  
  913. OSErr AutoFetchNewsrcFromHost (void)
  914. {
  915.     return GetGroupListFromHost(true);
  916. }
  917.  
  918.  
  919.  
  920. /*----------------------------------------------------------------------------
  921.     AppendStrToNewsrc 
  922.     
  923.     Append a string to the newsrc.
  924.  
  925.     Entry:    str = pointer to string to append to newsrc.
  926.     
  927.     Exit:    function result = error code.
  928. ----------------------------------------------------------------------------*/
  929.  
  930. static OSErr AppendStrToNewsrc (char *str)
  931. {
  932.      long len;
  933.      OSErr err = noErr;
  934.      
  935.      len = strlen(str);
  936.      if (gNewsrcLength + len > gNewsrcAllocated) {
  937.          gNewsrcAllocated += 10000;
  938.          err = MySetHandleSizeCritical(gNewsrc, gNewsrcAllocated);
  939.          if (err != noErr) return err;
  940.      }
  941.      BlockMoveData(str, *gNewsrc + gNewsrcLength, len);
  942.      gNewsrcLength += len;
  943.      return noErr;
  944. }
  945.  
  946.  
  947.  
  948. /*----------------------------------------------------------------------------
  949.     UpdateAllUnreadLists 
  950.     
  951.     Update all the unread lists for a user group list window.
  952.  
  953.     Entry:    wind = pointer to user group list window.
  954.  
  955.     Exit:    function result = error code.
  956. ----------------------------------------------------------------------------*/
  957.  
  958. OSErr UpdateAllUnreadLists (WindowPtr wind)
  959. {
  960.     TWindow **info;
  961.     TChild **childListEl;
  962.     OSErr err = noErr;
  963.      
  964.      info = (TWindow**)GetWRefCon(wind);
  965.      for (childListEl = (**info).childList; childListEl != nil; 
  966.          childListEl = (**childListEl).next) 
  967.      {
  968.          err = UpdateUnreadList((**childListEl).childWindow);
  969.          if (err != noErr) return err;
  970.      }
  971.      return noErr;
  972. }
  973.  
  974.  
  975.  
  976. /*----------------------------------------------------------------------------
  977.     MakeNewsrcFromUserGroupListWindow 
  978.     
  979.     Make a newsrc list from a user group list window.
  980.  
  981.     Entry:    wind = pointer to user group list window.
  982.  
  983.     Exit:    function result = error code.
  984.             *newsrc = handle to newsrc list.
  985.             *length = length of newsrc list.
  986. ----------------------------------------------------------------------------*/
  987.  
  988. static OSErr MakeNewsrcFromUserGroupListWindow (WindowPtr wind, 
  989.     Handle *newsrc, long *length)
  990. {
  991.      TWindow **info;
  992.      ListHandle theList;
  993.      TGroup **groupArray;
  994.      Cell theCell;
  995.      short numCells, index, cellDataLen;
  996.      TGroup theGroup;
  997.      long first, last;
  998.      TUnread **unread;
  999.      CStr255 tmpStr;
  1000.      Boolean firstRange;
  1001.      OSErr err = noErr;
  1002.      char state;
  1003.      
  1004.      state = MyHGetState(gGroupNames);
  1005.      
  1006.      err = MyNewHandleCritical(10000, &gNewsrc);
  1007.      if (err != noErr) return err;
  1008.      gNewsrcLength = 0;
  1009.      gNewsrcAllocated = 10000;
  1010.      
  1011.      info = (TWindow**)GetWRefCon(wind);
  1012.      theList = (**info).theList;
  1013.      groupArray = (**info).groupArray;
  1014.      
  1015.      err = UpdateAllUnreadLists(wind);
  1016.      if (err != noErr) goto exit;
  1017.      
  1018.      theCell.h = 0;
  1019.      numCells = (**theList).dataBounds.bottom;
  1020.      MyHLock(gGroupNames);
  1021.      for (theCell.v = 0; theCell.v < numCells; theCell.v++) {
  1022.          cellDataLen = 2;
  1023.          LGetCell(&index, &cellDataLen, theCell, theList);
  1024.          theGroup = (*groupArray)[index];
  1025.          err = AppendStrToNewsrc(*gGroupNames + theGroup.nameOffset);
  1026.          if (err != noErr) goto exit;
  1027.          err = AppendStrToNewsrc(": ");
  1028.          if (err != noErr) goto exit;
  1029.          first = 1;
  1030.          firstRange = true;
  1031.          for (unread = theGroup.unread; unread != nil; unread = (**unread).next) {
  1032.              last = (**unread).firstUnread-1;
  1033.              if (first <= last) {
  1034.                  if (first < last) {
  1035.                      sprintf(tmpStr, "%lu-%lu", first, last);
  1036.                  } else {
  1037.                      sprintf(tmpStr, "%lu", first);
  1038.                  }
  1039.                  if (!firstRange) {
  1040.                      err = AppendStrToNewsrc(",");
  1041.                      if (err != noErr) goto exit;
  1042.                  }
  1043.                  err = AppendStrToNewsrc(tmpStr);
  1044.                  if (err != noErr) goto exit;
  1045.                  firstRange = false;
  1046.              }
  1047.              first = (**unread).lastUnread + 1;
  1048.          }
  1049.          last = theGroup.lastMess;
  1050.          if (first <= last) {
  1051.              if (first < last) {
  1052.                  sprintf(tmpStr, "%lu-%lu", first, last);
  1053.              } else {
  1054.                  sprintf(tmpStr, "%lu", first);
  1055.              }
  1056.              if (!firstRange) {
  1057.                  err = AppendStrToNewsrc(",");
  1058.                  if (err != noErr) goto exit;
  1059.              }
  1060.              err = AppendStrToNewsrc(tmpStr);
  1061.              if (err != noErr) goto exit;
  1062.          }
  1063.          err = AppendStrToNewsrc(CRSTR);
  1064.          if (err != noErr) goto exit;
  1065.      }
  1066.      MyHSetState(gGroupNames, state);
  1067.           
  1068.      MySetHandleSize(gNewsrc, gNewsrcLength);
  1069.  
  1070.      *newsrc = gNewsrc;
  1071.      *length = gNewsrcLength;
  1072.      
  1073.      return noErr;
  1074.      
  1075. exit:
  1076.  
  1077.      MyHSetState(gGroupNames, state);
  1078.      MyDisposeHandle(gNewsrc);
  1079.      return err;
  1080. }
  1081.  
  1082.  
  1083.  
  1084. /*----------------------------------------------------------------------------
  1085.     SaveFile 
  1086.     
  1087.     Save a user group list window to a disk file.
  1088.  
  1089.     Entry:    wind = pointer to user group list window.
  1090.             fSpec = the file.
  1091.             scriptTag = script code.
  1092.  
  1093.     Exit:    function result = error code.
  1094. ----------------------------------------------------------------------------*/
  1095.  
  1096. static OSErr SaveFile (WindowPtr wind, FSSpec *fSpec, ScriptCode scriptTag)
  1097. {
  1098.     Handle newsrc = nil;
  1099.     long length;
  1100.     short fRefNum = 0;
  1101.     short rRefNum = 0;
  1102.     OSErr err = noErr;
  1103.     Boolean empty;
  1104.     Boolean savedCriticalSeq;
  1105.     
  1106.     BeginCriticalMemorySequence(&savedCriticalSeq);
  1107.  
  1108.     err = MakeNewsrcFromUserGroupListWindow(wind, &newsrc, &length);
  1109.     if (err != noErr) goto exit;
  1110.     
  1111.     err = OpenDataForkWriteCreateIfMissing(fSpec, kNewsWatcherSignature, 
  1112.         kSavedUserGroupListFileType, scriptTag, false, &fRefNum, &empty);
  1113.     if (err != noErr) goto exit;
  1114.     
  1115.     MyHLock(newsrc);
  1116.     err = MyFSWriteNoCache(fRefNum, &length, *newsrc, nil);
  1117.     MyDisposeHandle(newsrc);
  1118.     newsrc = nil;
  1119.     MyFSClose(fRefNum, nil);
  1120.     fRefNum = 0;
  1121.     
  1122.     err = OpenResFileWriteCreateIfMissing(fSpec, kNewsWatcherSignature, 
  1123.         kSavedUserGroupListFileType, scriptTag, &rRefNum);
  1124.     if (err != noErr) goto exit;
  1125.     
  1126.     err = WriteProgramNameResource(kStrNewsWatcher);
  1127.     if (err != noErr) goto exit;
  1128.     
  1129.     err = SaveWindPosAsResource(wind);
  1130.     if (err != noErr) goto exit;
  1131.     
  1132.     MyCloseResFile(rRefNum);
  1133.     
  1134.     EndCriticalMemorySequence(savedCriticalSeq);
  1135.     
  1136.     return noErr;
  1137.     
  1138. exit:
  1139.  
  1140.     MyDisposeHandle(newsrc);
  1141.     if (fRefNum != 0) MyFSClose(fRefNum, nil);
  1142.     if (rRefNum != 0) MyCloseResFile(rRefNum);
  1143.     EndCriticalMemorySequence(savedCriticalSeq);
  1144.     return err;
  1145. }
  1146.  
  1147.  
  1148.  
  1149. /*----------------------------------------------------------------------------
  1150.     DoSaveAs 
  1151.     
  1152.     Handle the "Save As" command.
  1153.  
  1154.     Entry:    wind = pointer to user group list window.
  1155.  
  1156.     Exit:    function result = error code.
  1157. ----------------------------------------------------------------------------*/
  1158.  
  1159. OSErr DoSaveAs (WindowPtr wind)
  1160. {
  1161.     StandardFileReply reply;
  1162.     TWindow **info;
  1163.     Str255 fName;
  1164.     Str255 prompt;
  1165.     OSErr err = noErr;
  1166.     AliasHandle alias;
  1167.     
  1168.     info = (TWindow**)GetWRefCon(wind);
  1169.     
  1170.     GetWTitle(wind, fName);
  1171.     GetPString(kStrSaveGroupListAs, prompt);
  1172.     MyStandardPutFile(prompt, fName, &reply, 
  1173.         gPrefs.savedUGLDefaultFolder ? gPrefs.savedUGLDefaultFolderAlias : nil);
  1174.     if (!reply.sfGood) return userCanceledErr;
  1175.         
  1176.     err = SaveFile(wind, &reply.sfFile, reply.sfScript);
  1177.     if (err != noErr) return err;
  1178.     SetWTitle(wind, reply.sfFile.name);
  1179.     err = NewAlias(nil, &reply.sfFile, &alias);
  1180.     if (err != noErr) return err;
  1181.     MyDisposeHandle((**info).alias);
  1182.     (**info).alias = alias;
  1183.     (**info).changed = false;
  1184.     return noErr;
  1185. }
  1186.  
  1187.  
  1188.  
  1189. /*----------------------------------------------------------------------------
  1190.     DoSave 
  1191.     
  1192.     Handle the "Save" command.
  1193.  
  1194.     Entry:    wind = pointer to user group list window.
  1195.  
  1196.     Exit:    function result = error code.
  1197. ----------------------------------------------------------------------------*/
  1198.  
  1199. OSErr DoSave (WindowPtr wind)
  1200. {
  1201.     TWindow **info;
  1202.     FSSpec fSpec;
  1203.     OSErr err = noErr;
  1204.     AliasHandle alias;
  1205.     Boolean wasChanged;
  1206.     
  1207.     info = (TWindow**)GetWRefCon(wind);
  1208.     alias = (**info).alias;
  1209.     if (alias != nil) {
  1210.         err = ResolveAlias(nil, alias, &fSpec, &wasChanged);
  1211.         if (err == noErr) {
  1212.             err = SaveFile(wind, &fSpec, smSystemScript);
  1213.             if (err != noErr) return err;
  1214.             (**info).changed = false;
  1215.             return noErr;
  1216.         } else {
  1217.             return DoSaveAs(wind);
  1218.         }
  1219.     } else {
  1220.         return DoSaveAs(wind);
  1221.     }
  1222. }
  1223.  
  1224.  
  1225.  
  1226. /*----------------------------------------------------------------------------
  1227.     CheckForSave 
  1228.     
  1229.     Ask the user if he wishes to save a user group list window, and save it 
  1230.     if the user says yes.
  1231.  
  1232.     Entry:    wind = pointer to user group list window.
  1233.  
  1234.     Exit:    function result = error code.
  1235. ----------------------------------------------------------------------------*/
  1236.  
  1237. OSErr CheckForSave (WindowPtr wind)
  1238. {
  1239.     TWindow **info;
  1240.     DialogPtr dlg = nil;
  1241.     short item;
  1242.     Str255 fName;
  1243.     OSErr err = noErr;
  1244.     
  1245.     info = (TWindow**)GetWRefCon(wind);
  1246.     if ((**info).okToCloseIfChanged) return noErr;
  1247.     if (gDone && gPrefs.autoSaveOnQuit && (**info).alias != nil) return DoSave(wind);
  1248.     GetWTitle(wind, fName);
  1249.     ParamText(fName, "\p", "\p", "\p");
  1250.     err = MyGetNewDialog(kCheckSaveID, ok, cancel, &dlg);
  1251.     if (err != noErr) return err;
  1252.     SetItemKeyEquivalent(dlg, 3, 'D');
  1253.     SysBeep(0);
  1254.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  1255.     err = DoClose(dlg);
  1256.     if (err != noErr) return err;
  1257.     switch (item) {
  1258.         case 1: /* save */
  1259.             return DoSave(wind);
  1260.         case 2: /* cancel */
  1261.             return userCanceledErr;
  1262.         case 3: /* don't save */
  1263.             return noErr;
  1264.     }
  1265.     return noErr;
  1266. }
  1267.  
  1268.  
  1269.  
  1270. /*----------------------------------------------------------------------------
  1271.     SendNewsrcFileToHost 
  1272.     
  1273.     FTP newsrc file to remote host.
  1274.  
  1275.     Entry:    autoFetch = true if sending back autofetched newsrc file.
  1276.             newsrc = handle to newsrc file.
  1277.             
  1278.     Exit:    function result = error code.
  1279. ----------------------------------------------------------------------------*/
  1280.  
  1281. static OSErr SendNewsrcFileToHost (Boolean autoFetch, Handle newsrc)
  1282. {
  1283.     FtpStreamRef stream;
  1284.     OSErr err = noErr;
  1285.     NetServerErrInfo serverErrInfo;
  1286.  
  1287.     err = OpenFTPStream(autoFetch, kStrSendingGroupListToHostStatusMsg, &stream);
  1288.     if (err != noErr) return err;
  1289.     
  1290.     err = FtpPutFile(stream, gPrefs.ftpNewsrcPath, true, newsrc);
  1291.     if (err != noErr) goto exit;
  1292.     
  1293.     err = FtpClose(stream);
  1294.     if (err != noErr) goto exit;
  1295.  
  1296.     return noErr;
  1297.     
  1298. exit:
  1299.  
  1300.     if (err == ftpServerErr) {
  1301.         FtpGetServerErrInfo(stream, &serverErrInfo);
  1302.         FtpClose(stream);
  1303.         err = ServerErrorMessage(kStrFTP, serverErrInfo.command, serverErrInfo.response);
  1304.         if (err != noErr) return err;
  1305.         return userCanceledErr;
  1306.     } else {
  1307.         SaveNetErrorInfo(kStrFTP, gPrefs.ftpNewsrcHost);
  1308.         return err;
  1309.     }
  1310. }
  1311.  
  1312.  
  1313.  
  1314. /*----------------------------------------------------------------------------
  1315.     DoSendGroupListToHost 
  1316.     
  1317.     Handle the "Send Group List to Host" command.
  1318.  
  1319.     Entry:    wind = pointer to window.
  1320.             autoFetch = true if sending back autofetched group list.
  1321.             
  1322.     Exit:    function result = error code.
  1323. ----------------------------------------------------------------------------*/
  1324.  
  1325. OSErr DoSendGroupListToHost (WindowPtr wind, Boolean autoFetch)
  1326. {
  1327.     TWindow **info;
  1328.     Handle newsrc = nil;
  1329.     Handle tempHandle = nil;
  1330.     Handle unsubscribed;
  1331.     long newsrcLen, unsubscribedLen;
  1332.     OSErr err = noErr;
  1333.     OSErr err1;
  1334.     Boolean savedCriticalSeq;
  1335.     
  1336.     BeginCriticalMemorySequence(&savedCriticalSeq);
  1337.     
  1338.     info = (TWindow**)GetWRefCon(wind);
  1339.     
  1340.     if (autoFetch) {
  1341.         strcpy(gPrefs.ftpNewsrcHost, gAutoFetchHost);
  1342.         strcpy(gPrefs.ftpNewsrcUsername, gAutoFetchUsername);
  1343.         strcpy(gPrefs.ftpNewsrcPassword, gAutoFetchPassword);
  1344.         strcpy(gPrefs.ftpNewsrcPath, gAutoFetchPath);
  1345.     }
  1346.     
  1347.     err = MakeNewsrcFromUserGroupListWindow(wind, &newsrc, &newsrcLen);
  1348.     if (err != noErr) goto exit;
  1349.  
  1350.     unsubscribed = (**info).unsubscribed;
  1351.     
  1352.     if (unsubscribed != nil) {
  1353.         unsubscribedLen = MyGetHandleSize(unsubscribed);
  1354.         if (!MemoryAvailable(unsubscribedLen)) {
  1355.             if (!HaveModernTempMemory()) {
  1356.                 err = memFullErr;
  1357.                 goto exit;
  1358.             }
  1359.             err = MyTempNewHandle(unsubscribedLen, &tempHandle);
  1360.             if (err != noErr) goto exit;
  1361.             BlockMoveData(*unsubscribed, *tempHandle, unsubscribedLen);
  1362.             MyDisposeHandle(unsubscribed);
  1363.             unsubscribed = tempHandle;
  1364.             (**info).unsubscribed = nil;
  1365.         }
  1366.         err = MyHandAndHand(unsubscribed, newsrc);
  1367.         if (err != noErr) goto exit;
  1368.     }
  1369.  
  1370.     err = SendNewsrcFileToHost(autoFetch, newsrc);
  1371.     if (err != noErr) goto exit;
  1372.         
  1373.     (**info).changed = false;
  1374.     
  1375. exit:
  1376.  
  1377.     MyDisposeHandle(newsrc);
  1378.     if (tempHandle != nil) {
  1379.         err1 = MyNewHandleCritical(unsubscribedLen, &unsubscribed);
  1380.         if (err1 == noErr) {
  1381.             BlockMoveData(*tempHandle, *unsubscribed, unsubscribedLen);
  1382.             (**info).unsubscribed = unsubscribed;
  1383.         }
  1384.         MyDisposeHandle(tempHandle);
  1385.     }
  1386.     EndCriticalMemorySequence(savedCriticalSeq);
  1387.     return err;
  1388. }
  1389.